#include "seq.h"

#include "arrayrep.h"
#include "mem.h"
#include "assert.h"

#include <stdarg.h>
#include <string.h>

#define T Seq_T

struct T {
    struct Array_T array; // the first field, as ring buffer
    int length;
    int head;
};

#define INDEX(seq, i) ((void**)seq->array.array)[(seq->head+i) % seq->array.length]

// new sequence
T Seq_new(int hint)
{
    assert(hint >= 0);

    T seq;
    NEW0(seq);

    if (hint == 0)
        hint = 16;
    int size = sizeof(void*);
    ArrayRep_init(&seq->array, hint, size, ALLOC(hint * size));
    return seq;
}

T Seq_seq(void *x, ...)
{
    va_list ap;
    T seq = Seq_new(0);

    va_start(ap, x);
    for (; x; x = va_arg(ap, void*))
        Seq_addhi(seq, x);
    va_end(ap);
    return seq;
}

void Seq_free(Seq_T *seq)
{
    assert(seq && *seq);
    assert((void*)*seq == (void*)&(*seq)->array);
    Array_free((Array_T*)seq);
}

int Seq_length(T seq)
{
    assert(seq);
    return seq->length;
}

void* Seq_get(T seq, int i)
{
    assert(seq);
    assert(i >= 0 && i < seq->length);
    return INDEX(seq, i);
}

void* Seq_put(T seq, int i, void *x)
{
    assert(seq);
    assert(i >= 0 && i < seq->length);
    void *prev = INDEX(seq, i);
    INDEX(seq, i) = x;
    return prev;
}

static void
expand(T seq)
{
    int n = seq->array.length;
    Array_resize(&seq->array, 2 * n);
    if (seq->head > 0) {
        // the array as ring buffer, leave the middle space empty
        void **old = &((void**)seq->array.array)[seq->head];
        memcpy(old + n, old, (n - seq->head) * sizeof(void*));
        seq->head += n;
    }
}

// add low
void* Seq_addlo(T seq, void *x)
{
    assert(seq);
    int i = 0;
    if (seq->length == seq->array.length)
        expand(seq);
    if (--seq->head < 0)
        seq->head = seq->array.length - 1;
    seq->length++;
    INDEX(seq, i) = x;
    return x;
}

// add high
void* Seq_addhi(T seq, void *x)
{
    assert(seq);
    if (seq->length == seq->array.length)
        expand(seq);
    int i = seq->length++;
    INDEX(seq, i) = x;
    return x;
}

// remove low
void* Seq_remlo(T seq)
{
    assert(seq);
    assert(seq->length > 0);
    int i = 0;
    void *x = INDEX(seq, i);
    seq->head = (seq->head + 1) % seq->array.length;
    --seq->length;
    return x;
}

// remove high
void* Seq_remhi(T seq)
{
    assert(seq);
    assert(seq->length > 0);
    int i = --seq->length;
    void *x = INDEX(seq, i);
    return x;
}

